package services.book;

import java.sql.SQLException;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.joda.time.DateTime;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.transaction.annotation.Transactional;

import repositories.BookInfoHomeRepository;
import services.GenericService;
import util.Pair;
import books.Book;
import books.BookInfo;
import books.BookInfoHome;
import books.BookRequestsInfo;
import books.BooksManager;
import dtos.BookInfoDTO;

/**
 */
public class BookService extends GenericService {

    private static final long serialVersionUID = 1L;

    private ReservationService reservationService;

    private BookInfoHomeRepository bookInfoHomeRepository;

    private BooksManager booksManager;

    public BooksManager getBooksManager() {
        return this.booksManager;
    }

    public void setBookInfoHomeRepository(final BookInfoHomeRepository bookInfoHomeRepository) {
        this.bookInfoHomeRepository = bookInfoHomeRepository;
    }

    public BookInfoHomeRepository getBookInfoHomeRepository() {
        return this.bookInfoHomeRepository;
    }

    public void setBooksManager(final BooksManager booksManager) {
        this.booksManager = booksManager;
    }

    public void setReservationService(final ReservationService reservationService) {
        this.reservationService = reservationService;
    }

    public ReservationService getReservationService() {
        return this.reservationService;
    }

    @Transactional
    public List<BookInfoDTO> searchByExampleDTO(final BookInfoDTO example) {
        BookInfo info = this.findById(example.getId());
        return this.getFactoryDto().toDtos(BookInfoDTO.class, this.getBookInfoHomeRepository().searchByExample(info));
    }

    @Transactional
    public List<BookInfo> searchByExample(final BookInfo example) {
        return this.getBookInfoHomeRepository().searchByExample(example);
    }

    @Transactional
    public List<BookInfoDTO> searchByTitle(final String title) {

        return this.getFactoryDto().toDtos(BookInfoDTO.class, this.getBookInfoHomeRepository().findByTitle(title));
    }

    private BookInfoHome getBookInfoHome(final BookInfo aBookInfo) {
        return this.getBookInfoHomeRepository().getBookInfoHome(aBookInfo);
    }

    @Transactional
    public BookInfo findBookByIsbn(final String anIsbn) {
        return this.getBookInfoHomeRepository().findBookByIsbn(anIsbn);
    }

    @Transactional(readOnly = true)
    public List<BookInfo> getAllBookInfos() {
        return this.getBookInfoHomeRepository().getBookInfos();
    }

    @Transactional(readOnly = true)
    public List<BookInfoDTO> getAllBooksDTO() {
        return this.getFactoryDto().toDtos(BookInfoDTO.class, this.getBookInfoHomeRepository().getBookInfos());
    }

    @Transactional
    public void addBookInfo(final BookInfo aBookInfo) {
        this.getBooksManager().validate(aBookInfo);
        this.getBookInfoHomeRepository().save(new BookInfoHome(aBookInfo));
        this.getReservationService().addBookInfoRequests(new BookRequestsInfo(aBookInfo));
    }

    @Transactional
    public void addBookInfoDTO(final BookInfoDTO aBookInfo) {
        BookInfo book = this.getFactoryDto().bookInfoDtoToModel(aBookInfo);
        book.setRegistrationDate(DateTime.now());
        this.getBooksManager().validate(book);
        this.getBookInfoHomeRepository().save(new BookInfoHome(book));
        this.getReservationService().addBookInfoRequests(new BookRequestsInfo(book));
    }

    @Transactional
    public Book borrowCopy(final BookInfo info) {
        return this.getBookInfoHome(info).borrowACopy();
    }

    @Transactional
    public List<Book> getAvailableCopies(final BookInfo aBookInfo) {
        return this.getBookInfoHome(aBookInfo).getAvailableCopies();

    }

    @Transactional
    public List<Book> getBorrowedCopies(final BookInfo aBookInfo) {
        return this.getBookInfoHome(aBookInfo).getBorrowedCopies();
    }

    @Transactional
    public int timesBorrowed(final BookInfo aBookInfo) {
        return this.getBookInfoHome(aBookInfo).getTimesBorrowed();
    }

    @Transactional
    public void addCopy(final Book aCopy) {
        // BookInfo toSave = this.findById(aCopy.getInfo().getId());
        BookInfo toSave = this.findBookByIsbn(aCopy.getInfo().getIsbn());
        aCopy.setInfo(toSave);
        this.getBookInfoHome(toSave).addNewCopy(aCopy);
        this.getReservationService().newCopyAvailable(aCopy);
    }

    // @Transactional
    // public List<BookInfo> findByExample(final BookInfo example) {
    // }

    @Transactional
    public void returnCopy(final Book aCopy) {
        Book toSave = this.getBookInfoHomeRepository().getHibernateTemplate().get(Book.class, aCopy.getId());

        this.getBookInfoHome(toSave.getInfo()).returnCopy(toSave);
        this.getReservationService().newCopyAvailable(toSave);
    }

    @Transactional
    public void updateBook(final BookInfoDTO aBookInfoDTO) {
        BookInfo bookDb = this.findBookByIsbn(aBookInfoDTO.getIsbn());
        bookDb = this.getFactoryDto().bookInfoDtoToModel(aBookInfoDTO, bookDb);

        this.getBooksManager().validate(bookDb);
        BookInfoHome infoHome = this.getBookInfoHome(bookDb);
        infoHome.setBookInfo(bookDb);
        this.getBookInfoHomeRepository().update(infoHome);
    }

    @Transactional
    public void deleteBookByIsbn(final String anIsbn) {
        BookInfo book = this.findBookByIsbn(anIsbn);
        this.getReservationService().deleteBook(book);
        this.getBookInfoHomeRepository().delete(this.getBookInfoHome(book));
    }

    @Transactional
    public BookInfo findById(final long id) {
        return this.getBookInfoHomeRepository().findBookInfoById(id);
    }

    @Transactional
    public Book getCopyById(final long id) {
        return (Book) this.getBookInfoHomeRepository().getHibernateTemplate().execute(new HibernateCallback<Object>() {
            @Override
            public Book doInHibernate(final Session session) throws HibernateException, SQLException {
                return (Book) session.get(Book.class, id);
            }
        });
    }

    @Transactional
    public List<Pair<BookInfo, Integer>> getTopX(final Integer anX) {
        return this.getBookInfoHomeRepository().getTopX(anX);
    }

    @Transactional
    public List<BookInfo> getTopBooks(final int maxResults) {
        return this.getBookInfoHomeRepository().getTopBooks(maxResults);
    }

    @Transactional
    public List<BookInfoDTO> getTopBooksDTO(final int maxResults) {
        return this.getFactoryDto().toDtos(BookInfoDTO.class, this.getBookInfoHomeRepository().getTopBooks(maxResults));
    }

    @Transactional(readOnly = true)
    public boolean copyAvailable(final BookInfo book) {
        return this.getBookInfoHome(book).areAvailableCopies();
    }

    @Transactional(readOnly = true)
    public boolean copyAvailableDTO(final BookInfoDTO book) {
        BookInfo info = this.findById(book.getId());
        return this.getBookInfoHome(info).areAvailableCopies();
    }

    @Transactional
    public List<BookInfoDTO> loanRelatedBooksDTO(final BookInfoDTO dto) {
        BookInfo info = this.findById(dto.getId());

        return this.getFactoryDto().toDtos(BookInfoDTO.class, this.getBookInfoHomeRepository().loanRelatedBooks(info));
    }

    @Transactional
    public List<BookInfoDTO> loanRelatedBooks(final BookInfo aBookInfo) {

        return this.getFactoryDto().toDtos(BookInfoDTO.class,
                this.getBookInfoHomeRepository().loanRelatedBooks(aBookInfo));
    }

    @Transactional
    public List<BookInfo> getNewBooks(final int maxResult) {
        return this.getBookInfoHomeRepository().getNewBooks(maxResult);
    }

    @Transactional
    public List<BookInfoDTO> getNewBooksDTO(final int maxResult) {
        return this.getFactoryDto().toDtos(BookInfoDTO.class, this.getBookInfoHomeRepository().getNewBooks(maxResult));
    }
}
